package jsr292.java.lang.invoke;

import jsr292.weaver.Debug;
import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerSnippets;
import jsr292.weaver.opt.OptimizerVarAllocator;

class MHCatcher extends MethodHandle {
    private final MethodHandle target;
    private final Class<? extends Throwable> exType;
    private final MethodHandle handler;

    public MHCatcher(MethodHandle target, Class<? extends Throwable> exType,
	    MethodHandle handler) {
	super(target.type());
	this.target = target;
	this.exType = exType;
	this.handler = handler;
    }

    @Override
    public Object $invoke$() throws Throwable {
	try {
	    return target.$invoke$();
	} catch (Throwable t) {
	    if (exType.isInstance(t)) {
		return handler.$invoke$(t);
	    }
	    throw t;
	}
    }

    @Override
    public Object $invoke$(Object o1) throws Throwable {
	try {
	    return target.$invoke$(o1);
	} catch (Throwable t) {
	    if (exType.isInstance(t)) {
		return handler.$invoke$(t, o1);
	    }
	    throw t;
	}
    }

    @Override
    public Object $invoke$(Object o1, Object o2) throws Throwable {
	try {
	    return target.$invoke$(o1, o2);
	} catch (Throwable t) {
	    if (exType.isInstance(t)) {
		return handler.$invoke$(t, o1, o2);
	    }
	    throw t;
	}
    }

    @Override
    public Object $invoke$(Object o1, Object o2, Object o3) throws Throwable {
	try {
	    return target.$invoke$(o1, o2, o3);
	} catch (Throwable t) {
	    if (exType.isInstance(t)) {
		return handler.$invoke$(t, o1, o2, o3);
	    }
	    throw t;
	}
    }

    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4)
	    throws Throwable {
	try {
	    return target.$invoke$(o1, o2, o3, o4);
	} catch (Throwable t) {
	    if (exType.isInstance(t)) {
		return handler.$invoke$(t, o1, o2, o3, o4);
	    }
	    throw t;
	}
    }

    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5)
	    throws Throwable {
	try {
	    return target.$invoke$(o1, o2, o3, o4, o5);
	} catch (Throwable t) {
	    if (exType.isInstance(t)) {
		return handler.$invoke$(t, o1, o2, o3, o4, o5);
	    }
	    throw t;
	}
    }

    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4,
	    Object o5, Object o6) throws Throwable {
	try {
	    return target.$invoke$(o1, o2, o3, o4, o5, o6);
	} catch (Throwable t) {
	    if (exType.isInstance(t)) {
		return handler.$invoke$(t, o1, o2, o3, o4, o5, o6);
	    }
	    throw t;
	}
    }

    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4,
	    Object o5, Object o6, Object o7) throws Throwable {
	try {
	    return target.$invoke$(o1, o2, o3, o4, o5, o6, o7);
	} catch (Throwable t) {
	    if (exType.isInstance(t)) {
		return handler.$invoke$(t, o1, o2, o3, o4, o5, o6, o7);
	    }
	    throw t;
	}
    }

    @Override
    public Object $invoke$(Object... rest) throws Throwable {
	try {
	    return target.$invoke$(rest);
	} catch (Throwable t) {
	    if (exType.isInstance(t)) {
		Object[] array = new Object[rest.length + 1];
		System.arraycopy(rest, 0, array, 1, rest.length);
		array[0] = t;
		return handler.$invoke$(array);
	    }
	    throw t;
	}
    }

    @Override
    public boolean isWeavable(Class<?> classBeingRedefined) {
        return target.isWeavable(classBeingRedefined) && handler.isWeavable(classBeingRedefined);
    }
    @Override
    public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
        if (Debug.DEBUG) {
            Debug.checkAllocator(allocator, this);
        }
        
	OptimizerSnippets.catchz(context, allocator, target, handler, exType);
    }
}
